/*
 ------------------------------------------------------------------------------
 Copyright (C) 2009-2011 Eternal Games.

 This file is part of the Eternal Tech source code.

 The Eternal Tech source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The Eternal Tech source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the Eternal Tech source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


//
// r_backEnd.cpp - ...
//


#include "r_local.h"


backEndState_t backEnd;


/*
 ==============================================================================

    VERTEX BUFFER OBJECTS

 ==============================================================================
*/

static vertexBuffer_t	rb_vertexBuffers[MAX_VERTEX_BUFFERS];
static int				rb_numVertexBuffers;


/*
 ==================
 RB_DrawElements
 ==================
*/
void RB_DrawElements ()
{
	index_t	*indices;

	rg.pc.draws++;

	if (!backEnd.stencilShadow)
	{
		rg.pc.indices += backEnd.numIndices;
		rg.pc.vertices += backEnd.numVertices;

		indices = backEnd.indices;
	}
	else
	{
		rg.pc.shadowIndices += backEnd.numIndices;
		rg.pc.shadowVertices += backEnd.numVertices;

		indices = backEnd.shadowIndices;
	}

	if (glConfig.drawRangeElements)
		qglDrawRangeElementsEXT(GL_TRIANGLES, 0, backEnd.numVertices, backEnd.numIndices, GL_UNSIGNED_INT, indices);
	else
		qglDrawElements(GL_TRIANGLES, backEnd.numIndices, GL_UNSIGNED_INT, indices);
}

/*
 ==================
 RB_UpdateVertexBuffer
 ==================
*/
void RB_UpdateVertexBuffer (vertexBuffer_t *vertexBuffer, const void *data, int size)
{
	rg.pc.vertexBuffers++;
	rg.pc.vertexBufferBytes += size;

	if (!glConfig.vertexBufferObject)
	{
		vertexBuffer->pointer = (char *)data;
		return;
	}

	vertexBuffer->pointer = NULL;

	qglBindBufferARB(GL_ARRAY_BUFFER_ARB, vertexBuffer->bufNum);
	qglBufferDataARB(GL_ARRAY_BUFFER_ARB, size, data, vertexBuffer->usage);
}

/*
 ==================
 RB_AllocVertexBuffer
 ==================
*/
static vertexBuffer_t *RB_AllocVertexBuffer (int size, uint usage)
{
	vertexBuffer_t	*vertexBuffer;

	if (rb_numVertexBuffers == MAX_VERTEX_BUFFERS)
		Com_Error(ERR_DROP, "RB_AllocVertexBuffer: MAX_VERTEX_BUFFERS hit");

	vertexBuffer = &rb_vertexBuffers[rb_numVertexBuffers++];

	vertexBuffer->pointer = NULL;
	vertexBuffer->size = size;
	vertexBuffer->usage = usage;

	if (!glConfig.vertexBufferObject)
		return vertexBuffer;

	qglGenBuffersARB(1, &vertexBuffer->bufNum);
	qglBindBufferARB(GL_ARRAY_BUFFER_ARB, vertexBuffer->bufNum);
	qglBufferDataARB(GL_ARRAY_BUFFER_ARB, vertexBuffer->size, NULL, vertexBuffer->usage);

	return vertexBuffer;
}

/*
 ==================
 RB_InitVertexBuffers
 ==================
*/
static void RB_InitVertexBuffers ()
{
	backEnd.vertexBuffer = RB_AllocVertexBuffer(MAX_VERTICES * sizeof(vertexArray_t), GL_STREAM_DRAW_ARB);
	backEnd.shadowVertexBuffer = RB_AllocVertexBuffer(MAX_SHADOW_VERTICES * sizeof(shadowVertexArray_t), GL_STREAM_DRAW_ARB);
}

/*
 ==================
 RB_ShutdownVertexBuffers
 ==================
*/
static void RB_ShutdownVertexBuffers ()
{
	vertexBuffer_t   *vertexBuffer;
	int				 i;

	if (!glConfig.vertexBufferObject)
	{
		memset(rb_vertexBuffers, 0, sizeof(rb_vertexBuffers));

		rb_numVertexBuffers = 0;
		return;
	}

	qglBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);

	for (i = 0, vertexBuffer = rb_vertexBuffers; i < rb_numVertexBuffers; i++, vertexBuffer++)
		qglDeleteBuffersARB(1, &vertexBuffer->bufNum);

	memset(rb_vertexBuffers, 0, sizeof(rb_vertexBuffers));

	rb_numVertexBuffers = 0;
}


/*
 ==============================================================================

    ENTITY AND LIGHT STATES

 ==============================================================================
*/


/*
 ==================
 RB_SetFogLightMatrix
 ==================
*/
static void RB_SetFogLightMatrix (renderEntity_t *entity, light_t *light, material_t *lightMaterial, stage_t *lightStage)
{
	vec4_t	distanceVector, depthVector, viewDepthVector;
	float	distanceScale, depthScale;

	// Compute the fog distance and depth scales
	distanceScale = lightMaterial->expressionRegisters[lightStage->colorStage.registers[3]];

	if (distanceScale <= 1.0)
		distanceScale = 0.5f / 500;
	else
		distanceScale = 0.5f / distanceScale;

	depthScale = 0.5f / 500;

	// Set up the distance vector
	distanceVector[0] = backEnd.localParms.viewMatrix[ 2] * distanceScale;
	distanceVector[1] = backEnd.localParms.viewMatrix[ 6] * distanceScale;
	distanceVector[2] = backEnd.localParms.viewMatrix[10] * distanceScale;
	distanceVector[3] = backEnd.localParms.viewMatrix[14] * distanceScale + 0.5f;

	// Set up the depth vector
	if (entity == rg.worldEntity)
	{
		depthVector[0] = light->planeNormal[0] * depthScale;
		depthVector[1] = light->planeNormal[1] * depthScale;
		depthVector[2] = light->planeNormal[2] * depthScale;
		depthVector[3] = -light->planeDist * depthScale + 0.5f;
	}
	else
	{
		depthVector[0] = DotProduct(light->planeNormal, entity->axis[0]) * depthScale;
		depthVector[1] = DotProduct(light->planeNormal, entity->axis[1]) * depthScale;
		depthVector[2] = DotProduct(light->planeNormal, entity->axis[2]) * depthScale;
		depthVector[3] = (DotProduct(entity->origin, light->planeNormal) - light->planeDist) * depthScale + 0.5f;
	}

	// Set up the view-depth vector
	viewDepthVector[0] = 0.0f;
	viewDepthVector[1] = 0.0f;
	viewDepthVector[2] = 0.0f;
	viewDepthVector[3] = (DotProduct(backEnd.viewParms.origin, light->planeNormal) - light->planeDist) * depthScale + 0.5f;

	// Set up the object planes
	GL_SelectTexture(0);

	qglTexGenfv(GL_S, GL_OBJECT_PLANE, distanceVector);
	qglTexGenfv(GL_T, GL_OBJECT_PLANE, distanceVector);

	GL_SelectTexture(1);

	qglTexGenfv(GL_S, GL_OBJECT_PLANE, depthVector);
	qglTexGenfv(GL_T, GL_OBJECT_PLANE, viewDepthVector);
}

/*
 ==================
 RB_SetBlendLightMatrix
 ==================
*/
static void RB_SetBlendLightMatrix (renderEntity_t *entity, light_t *light, material_t *lightMaterial, stage_t *lightStage)
{
	mat4_t	transformMatrix, entityMatrix, textureMatrix;
	mat4_t	lightMatrix, tmpMatrix;

	// Set up the light matrix
	if (entity == rg.worldEntity)
	{
		if (!lightStage->textureStage.numTexTransforms)
			Matrix4_Transpose(light->matrix, lightMatrix);
		else
		{
			RB_MultiplyTextureMatrix(lightMaterial, &lightStage->textureStage, textureMatrix);

			Matrix4_MultiplyFast(textureMatrix, light->matrix, tmpMatrix);
			Matrix4_Transpose(tmpMatrix, lightMatrix);
		}
	}
	else
	{
		Matrix4_Set(transformMatrix, entity->axis, entity->origin);
		Matrix4_MultiplyFast(light->matrix, transformMatrix, entityMatrix);

		if (!lightStage->textureStage.numTexTransforms)
			Matrix4_Transpose(entityMatrix, lightMatrix);
		else
		{
			RB_MultiplyTextureMatrix(lightMaterial, &lightStage->textureStage, textureMatrix);

			Matrix4_MultiplyFast(textureMatrix, entityMatrix, tmpMatrix);
			Matrix4_Transpose(tmpMatrix, lightMatrix);
		}
	}

	// Set up the object planes
	GL_SelectTexture(0);

	qglTexGenfv(GL_S, GL_OBJECT_PLANE, &lightMatrix[ 0]);
	qglTexGenfv(GL_T, GL_OBJECT_PLANE, &lightMatrix[ 4]);
	qglTexGenfv(GL_Q, GL_OBJECT_PLANE, &lightMatrix[12]);

	GL_SelectTexture(1);

	qglTexGenfv(GL_S, GL_OBJECT_PLANE, &lightMatrix[ 8]);
}

/*
 ==================
 RB_SetLightMatrix
 ==================
*/
static void RB_SetLightMatrix (renderEntity_t *entity, light_t *light, material_t *lightMaterial, stage_t *lightStage)
{
	mat4_t	transformMatrix, entityMatrix, textureMatrix;
	mat4_t	tmpMatrix;

	// Set up the light matrix
	if (entity == rg.worldEntity)
	{
		if (!lightStage->textureStage.numTexTransforms)
			Matrix4_Transpose(light->matrix, backEnd.localParms.lightMatrix);
		else
		{
			RB_MultiplyTextureMatrix(lightMaterial, &lightStage->textureStage, textureMatrix);

			Matrix4_MultiplyFast(textureMatrix, light->matrix, tmpMatrix);
			Matrix4_Transpose(tmpMatrix, backEnd.localParms.lightMatrix);
		}
	}
	else
	{
		Matrix4_Set(transformMatrix, entity->axis, entity->origin);
		Matrix4_MultiplyFast(light->matrix, transformMatrix, entityMatrix);

		if (!lightStage->textureStage.numTexTransforms)
			Matrix4_Transpose(entityMatrix, backEnd.localParms.lightMatrix);
		else
		{
			RB_MultiplyTextureMatrix(lightMaterial, &lightStage->textureStage, textureMatrix);

			Matrix4_MultiplyFast(textureMatrix, entityMatrix, tmpMatrix);
			Matrix4_Transpose(tmpMatrix, backEnd.localParms.lightMatrix);
		}
	}
}

/*
 ==================
 RB_SetLightState
 ==================
*/
void RB_SetLightState (renderEntity_t *entity, light_t *light, material_t *lightMaterial, stage_t *lightStage)
{
	vec3_t	tmpOrigin;

	// Transform light origin to local space
	if (entity == rg.worldEntity)
		VectorCopy(light->origin, backEnd.localParms.lightOrigin);
	else
	{
		VectorSubtract(light->origin, entity->origin, tmpOrigin);
		VectorRotate(tmpOrigin, entity->axis, backEnd.localParms.lightOrigin);
	}

	// Set up the light matrix if needed
	if (!lightMaterial || !lightStage)
		return;

	if (lightMaterial->fogLight)
		RB_SetFogLightMatrix(entity, light, lightMaterial, lightStage);
	else if (lightMaterial->blendLight)
		RB_SetBlendLightMatrix(entity, light, lightMaterial, lightStage);
	else
		RB_SetLightMatrix(entity, light, lightMaterial, lightStage);
}

/*
 ==================
 RB_SetEntityState

 TODO: backEnd.localParms.viewAxis
 ==================
*/
void RB_SetEntityState (renderEntity_t *entity)
{
	mat4_t	transformMatrix;
	vec3_t	tmpOrigin;

	if (entity == rg.worldEntity)
	{
		VectorCopy(backEnd.viewParms.origin, backEnd.localParms.viewOrigin);
		Matrix4_Copy(backEnd.viewParms.worldMatrix, backEnd.localParms.viewMatrix);

		GL_DepthRange(0.0f, 1.0f);
	}
	else
	{
		VectorSubtract(backEnd.viewParms.origin, entity->origin, tmpOrigin);
		VectorRotate(tmpOrigin, entity->axis, backEnd.localParms.viewOrigin);

		if (entity->reType == RE_MODEL)
		{
			Matrix4_Set(transformMatrix, entity->axis, entity->origin);
			Matrix4_MultiplyFast(backEnd.viewParms.worldMatrix, transformMatrix, backEnd.localParms.viewMatrix);
		}
		else
			Matrix4_Copy(backEnd.viewParms.worldMatrix, backEnd.localParms.viewMatrix);

		if (entity->depthHack)
			GL_DepthRange(0.0f, entity->depthHack);
		else
			GL_DepthRange(0.0f, 1.0f);
	}

	GL_LoadMatrix(GL_MODELVIEW, backEnd.localParms.viewMatrix);
}


/*
 ==============================================================================

    RENDER COMMANDS EXECUTION

 ==============================================================================
*/


/*
 ==================
 RB_RenderView
 ==================
*/
static const void *RB_RenderView (const renderViewCommand_t *cmd)
{
	// Render any remaining pics
	if (backEnd.projection2D)
		RB_RenderBatch();

	if (r_logFile->integerValue)
		QGL_LogPrintf("---------- RB_RenderView ----------\n");

	rg.pc.views++;

	// Set the state
	backEnd.time = cmd->time;
	memcpy(backEnd.materialParms, cmd->materialParms, MAX_MATERIAL_PARMS * sizeof(float));

	backEnd.viewport = cmd->viewport;
	backEnd.viewParms = cmd->viewParms;

	// Switch to 3D mode
	GL_Setup3DView();

	// Z-fill pass
	RB_FillDepthBuffer(cmd->meshes, cmd->numMeshes);

	// Stencil shadow & light interaction pass
	RB_RenderLights(cmd->lights, cmd->numLights);

	// Ambient pass (opaque & translucent)
	RB_RenderMaterialPasses(cmd->meshes, cmd->numMeshes, false);

	// Fog & blend light interaction pass
	RB_FogAllLights(cmd->fogLights, cmd->numFogLights);

	// Ambient pass (post-process)
	RB_RenderMaterialPasses(cmd->postProcessMeshes, cmd->numPostProcessMeshes, true);

	// Render debug tools
	RB_RenderDebugTools(cmd);

	return (const void *)(cmd + 1);
}

/*
 ==================
 RB_CaptureRender
 ==================
*/
static const void *RB_CaptureRender (const captureRenderCommand_t *cmd)
{
	texture_t	*texture = cmd->texture;

	if (r_skipCopyToTextures->integerValue)
		return (const void *)(cmd + 1);

	// Render any remaining pics
	if (backEnd.projection2D)
		RB_RenderBatch();

	if (r_logFile->integerValue)
		QGL_LogPrintf("---------- RB_CaptureRender ----------\n");

	rg.pc.captureRenders++;
	rg.pc.captureRenderPixels += backEnd.renderWidth * backEnd.renderHeight;

	// Update the texture
	GL_BindTexture(texture);

	if (backEnd.renderWidth == texture->uploadWidth && backEnd.renderHeight == texture->uploadHeight)
		qglCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, backEnd.renderWidth, backEnd.renderHeight);
	else
	{
		// Check the dimensions
		if (!glConfig.textureNonPowerOfTwo)
		{
			if (!IsPowerOfTwo(backEnd.renderWidth) || !IsPowerOfTwo(backEnd.renderHeight))
				Com_Error(ERR_DROP, "RB_CaptureRender: size is not a power of two (%i x %i)", backEnd.renderWidth, backEnd.renderHeight);
		}

		if (backEnd.renderWidth > glConfig.maxTextureSize || backEnd.renderHeight > glConfig.maxTextureSize)
			Com_Error(ERR_DROP, "RB_CaptureRender: size exceeds hardware limits (%i > %i or %i > %i)", backEnd.renderWidth, glConfig.maxTextureSize, backEnd.renderHeight, glConfig.maxTextureSize);

		// Reallocate the texture
		texture->uploadWidth = backEnd.renderWidth;
		texture->uploadHeight = backEnd.renderHeight;

		if (texture->sourceSamples >= 3)
			texture->uploadSize = backEnd.renderWidth * backEnd.renderHeight * 4;
		else
			texture->uploadSize = backEnd.renderWidth * backEnd.renderHeight * texture->sourceSamples;

		qglCopyTexImage2D(GL_TEXTURE_2D, 0, texture->uploadFormat, 0, 0, backEnd.renderWidth, backEnd.renderHeight, 0);
	}

	return (const void *)(cmd + 1);
}

/*
 ==================
 RB_UpdateTexture
 ==================
*/
static const void *RB_UpdateTexture (const updateTextureCommand_t *cmd)
{
	texture_t	*texture = cmd->texture;

	if (r_skipDynamicTextures->integerValue)
		return (const void *)(cmd + 1);

	// Render any remaining pics
	if (backEnd.projection2D)
		RB_RenderBatch();

	if (r_logFile->integerValue)
		QGL_LogPrintf("---------- RB_UpdateTexture ----------\n");

	rg.pc.updateTextures++;
	rg.pc.updateTexturePixels += cmd->width * cmd->height;

	// Update the texture
	GL_BindTexture(texture);

	if (cmd->width == texture->uploadWidth && cmd->height == texture->uploadHeight)
		qglTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, cmd->width, cmd->height, GL_RGBA, GL_UNSIGNED_BYTE, cmd->image);
	else
	{
		// Check the dimensions
		if (!glConfig.textureNonPowerOfTwo)
		{
			if (!IsPowerOfTwo(cmd->width) || !IsPowerOfTwo(cmd->height))
				Com_Error(ERR_DROP, "RB_UpdateTexture: size is not a power of two (%i x %i)", cmd->width, cmd->height);
		}

		if (cmd->width > glConfig.maxTextureSize || cmd->height > glConfig.maxTextureSize)
			Com_Error(ERR_DROP, "RB_UpdateTexture: size exceeds hardware limits (%i > %i or %i > %i)", cmd->width, glConfig.maxTextureSize, cmd->height, glConfig.maxTextureSize);

		// Reallocate the texture
		texture->uploadWidth = cmd->width;
		texture->uploadHeight = cmd->height;

		if (texture->sourceSamples >= 3)
			texture->uploadSize = cmd->width * cmd->height * 4;
		else
			texture->uploadSize = cmd->width * cmd->height * texture->sourceSamples;

		qglTexImage2D(GL_TEXTURE_2D, 0, texture->uploadFormat, cmd->width, cmd->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, cmd->image);
	}

	return (const void *)(cmd + 1);
}

/*
 ==================
 RB_StretchPic
 ==================
*/
static const void *RB_StretchPic (const stretchPicCommand_t *cmd)
{
	material_t		*material = cmd->material;
	index_t			*indices;
	vertexArray_t	*vertices;
	int				i;

	// Switch to 2D mode if needed
	if (!backEnd.projection2D)
	{
		GL_Setup2DView();

		// Set the render function
		backEnd.renderBatch = RB_RenderOverlayMaterial;

		// Clear the batch state
		backEnd.material = NULL;
		backEnd.entity = NULL;

		backEnd.stencilShadow = false;
		backEnd.shadowCaps = false;
	}

	// Check if the material changed
	if (material != backEnd.material)
	{
		// Render the last batch
		RB_RenderBatch();

		// Evaluate registers
		R_EvaluateRegisters(material, backEnd.time, rg.worldEntity->materialParms, backEnd.materialParms);

		// Skip if condition evaluated to false
		if (!material->expressionRegisters[material->conditionRegister])
			return (const void *)(cmd + 1);

		// Create a new batch
		RB_SetupBatch(material, rg.worldEntity, false, false);
	}

	// Skip if condition evaluated to false
	if (!backEnd.material->expressionRegisters[backEnd.material->conditionRegister])
		return (const void *)(cmd + 1);

	// Check for overflow
	RB_CheckOverflow(6, 4, MAX_INDICES, MAX_VERTICES);

	// Set up indices
	indices = backEnd.indices + backEnd.numIndices;

	for (i = 2; i < 4; i++)
	{
		indices[0] = backEnd.numVertices + 0;
		indices[1] = backEnd.numVertices + i-1;
		indices[2] = backEnd.numVertices + i;

		indices += 3;
	}

	backEnd.numIndices += 6;

	// Set up vertices
	vertices = backEnd.vertices + backEnd.numVertices;

	vertices[0].xyz[0] = cmd->x;
	vertices[0].xyz[1] = cmd->y;
	vertices[0].xyz[2] = 0.0f;
	vertices[1].xyz[0] = cmd->x + cmd->w;
	vertices[1].xyz[1] = cmd->y;
	vertices[1].xyz[2] = 0.0f;
	vertices[2].xyz[0] = cmd->x + cmd->w;
	vertices[2].xyz[1] = cmd->y + cmd->h;
	vertices[2].xyz[2] = 0.0f;
	vertices[3].xyz[0] = cmd->x;
	vertices[3].xyz[1] = cmd->y + cmd->h;
	vertices[3].xyz[2] = 0.0f;

	vertices[0].st[0] = cmd->s1;
	vertices[0].st[1] = cmd->t1;
	vertices[1].st[0] = cmd->s2;
	vertices[1].st[1] = cmd->t1;
	vertices[2].st[0] = cmd->s2;
	vertices[2].st[1] = cmd->t2;
	vertices[3].st[0] = cmd->s1;
	vertices[3].st[1] = cmd->t2;

	for (i = 0; i < 4; i++)
	{
		vertices->color[0] = cmd->color[0];
		vertices->color[1] = cmd->color[1];
		vertices->color[2] = cmd->color[2];
		vertices->color[3] = cmd->color[3];

		vertices++;
	}

	backEnd.numVertices += 4;

	return (const void *)(cmd + 1);
}

/*
 ==================
 RB_ShearedPic
 ==================
*/
static const void *RB_ShearedPic (const shearedPicCommand_t *cmd)
{
	material_t		*material = cmd->material;
	index_t			*indices;
	vertexArray_t	*vertices;
	int				i;

	// Switch to 2D mode if needed
	if (!backEnd.projection2D)
	{
		GL_Setup2DView();

		// Set the render function
		backEnd.renderBatch = RB_RenderOverlayMaterial;

		// Clear the batch state
		backEnd.material = NULL;
		backEnd.entity = NULL;

		backEnd.stencilShadow = false;
		backEnd.shadowCaps = false;
	}

	// Check if the material changed
	if (material != backEnd.material)
	{
		// Render the last batch
		RB_RenderBatch();

		// Evaluate registers
		R_EvaluateRegisters(material, backEnd.time, rg.worldEntity->materialParms, backEnd.materialParms);

		// Skip if condition evaluated to false
		if (!material->expressionRegisters[material->conditionRegister])
			return (const void *)(cmd + 1);

		// Create a new batch
		RB_SetupBatch(material, rg.worldEntity, false, false);
	}

	// Skip if condition evaluated to false
	if (!backEnd.material->expressionRegisters[backEnd.material->conditionRegister])
		return (const void *)(cmd + 1);

	// Check for overflow
	RB_CheckOverflow(6, 4, MAX_INDICES, MAX_VERTICES);

	// Set up indices
	indices = backEnd.indices + backEnd.numIndices;

	for (i = 2; i < 4; i++)
	{
		indices[0] = backEnd.numVertices + 0;
		indices[1] = backEnd.numVertices + i-1;
		indices[2] = backEnd.numVertices + i;

		indices += 3;
	}

	backEnd.numIndices += 6;

	// Set up vertices
	vertices = backEnd.vertices + backEnd.numVertices;

	vertices[0].xyz[0] = cmd->x + cmd->shearX;
	vertices[0].xyz[1] = cmd->y + cmd->shearY;
	vertices[0].xyz[2] = 0.0f;
	vertices[1].xyz[0] = cmd->x + cmd->w + cmd->shearX;
	vertices[1].xyz[1] = cmd->y - cmd->shearY;
	vertices[1].xyz[2] = 0.0f;
	vertices[2].xyz[0] = cmd->x + cmd->w - cmd->shearX;
	vertices[2].xyz[1] = cmd->y + cmd->h - cmd->shearY;
	vertices[2].xyz[2] = 0.0f;
	vertices[3].xyz[0] = cmd->x - cmd->shearX;
	vertices[3].xyz[1] = cmd->y + cmd->h + cmd->shearY;
	vertices[3].xyz[2] = 0.0f;

	vertices[0].st[0] = cmd->s1;
	vertices[0].st[1] = cmd->t1;
	vertices[1].st[0] = cmd->s2;
	vertices[1].st[1] = cmd->t1;
	vertices[2].st[0] = cmd->s2;
	vertices[2].st[1] = cmd->t2;
	vertices[3].st[0] = cmd->s1;
	vertices[3].st[1] = cmd->t2;

	for (i = 0; i < 4; i++)
	{
		vertices->color[0] = cmd->color[0];
		vertices->color[1] = cmd->color[1];
		vertices->color[2] = cmd->color[2];
		vertices->color[3] = cmd->color[3];

		vertices++;
	}

	backEnd.numVertices += 4;

	return (const void *)(cmd + 1);
}

/*
 ==================
 RB_RenderSize
 ==================
*/
static const void *RB_RenderSize (const renderSizeCommand_t *cmd)
{
	// Render any remaining pics
	if (backEnd.projection2D)
		RB_RenderBatch();

	// Force a mode switch
	backEnd.projection2D = false;

	// Set the render size
	backEnd.renderWidth = cmd->width;
	backEnd.renderHeight = cmd->height;

	return (const void *)(cmd + 1);
}

/*
 ==================
 RB_DrawBuffer
 ==================
*/
static const void *RB_DrawBuffer (const drawBufferCommand_t *cmd)
{
	float	r, g, b;

	// Set the draw buffer
	if (r_frontBuffer->integerValue)
		qglDrawBuffer(GL_FRONT);
	else
		qglDrawBuffer(GL_BACK);

	// Clear screen if desired
	if (r_clear->integerValue)
	{
		r = colorTable[r_clearColor->integerValue & Q_COLOR_MASK][0] * (1.0f/255);
		g = colorTable[r_clearColor->integerValue & Q_COLOR_MASK][1] * (1.0f/255);
		b = colorTable[r_clearColor->integerValue & Q_COLOR_MASK][2] * (1.0f/255);

		GL_ColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);

		qglClearColor(r, g, b, 1.0);
		qglClear(GL_COLOR_BUFFER_BIT);
	}

	// Set the render size
	backEnd.renderWidth = cmd->width;
	backEnd.renderHeight = cmd->height;

	// Check for errors
	if (!r_ignoreGLErrors->integerValue)
		GL_CheckForErrors();

	return (const void *)(cmd + 1);
}

/*
 ==================
 RB_SwapBuffers
 ==================
*/
static const void *RB_SwapBuffers (const swapBuffersCommand_t *cmd)
{
	// Render any remaining pics
	if (backEnd.projection2D)
		RB_RenderBatch();

	// Force a mode switch
	backEnd.projection2D = false;

	// Swap the buffers
	if (r_finish->integerValue)
		qglFinish();

	GLImp_SwapBuffers();

	// Check for errors
	if (!r_ignoreGLErrors->integerValue)
		GL_CheckForErrors();

	// Log file
	if (r_logFile->integerValue)
		QGL_LogPrintf("\n========== RB_SwapBuffers ==========\n\n");

	return (const void *)(cmd + 1);
}

/*
 ==================
 RB_ExecuteRenderCommands

 TODO: Add a "RB_SetColor" command?, instaid of looping through all four colors
 every frame in RB_StretchPic and RB_ShearedPic
 ==================
*/
static void RB_ExecuteRenderCommands (const void *data)
{
	while (1){
		switch (*(const int *)data){
		case RC_RENDER_VIEW:
			data = RB_RenderView((const renderViewCommand_t*)data);

			break;
		case RC_CAPTURE_RENDER:
			data = RB_CaptureRender((const captureRenderCommand_t*)data);

			break;
		case RC_UPDATE_TEXTURE:
			data = RB_UpdateTexture((const updateTextureCommand_t*)data);

			break;
		case RC_STRETCH_PIC:
			data = RB_StretchPic((const stretchPicCommand_t*)data);

			break;
		case RC_SHEARED_PIC:
			data = RB_ShearedPic((const shearedPicCommand_t*)data);

			break;
		case RC_RENDER_SIZE:
			data = RB_RenderSize((const renderSizeCommand_t*)data);

			break;
		case RC_DRAW_BUFFER:
			data = RB_DrawBuffer((const drawBufferCommand_t*)data);

			break;
		case RC_SWAP_BUFFERS:
			data = RB_SwapBuffers((const swapBuffersCommand_t*)data);

			break;
		case RC_END_OF_LIST:
			//if (r_showRenderTime->integerValue)
			//	tr.pc.timeBackEnd += (Sys_GetClockTicks() - time);

			return;
		default:
			Com_Error(ERR_FATAL, "RB_ExecuteRenderCommands: bad commandId (%i)", *(const int *)data);
		}
	}
}

/*
 ==================
 RB_ClearRenderCommands
 ==================
*/
void RB_ClearRenderCommands ()
{
	renderCommandList_t	*commandList = &backEnd.commandList;

	// Clear it out
	commandList->used = 0;
}

/*
 ==================
 RB_IssueRenderCommands
 ==================
*/
void RB_IssueRenderCommands ()
{
	renderCommandList_t	*commandList = &backEnd.commandList;

	if (!commandList->used)
		return;

	// Add an end of list command
	*(int *)(commandList->data + commandList->used) = RC_END_OF_LIST;

	// Execute the commands
	RB_ExecuteRenderCommands(commandList->data);
}

/*
 ==================
 RB_GetCommandBuffer
 ==================
*/
void *RB_GetCommandBuffer (int size)
{
	renderCommandList_t	*commandList = &backEnd.commandList;

	// Always leave room for the end of list command
	if (commandList->used + size + sizeof(int) > MAX_RENDER_COMMANDS)
	{
		if (size > MAX_RENDER_COMMANDS - sizeof(int))
			Com_Error(ERR_FATAL, "RB_GetCommandBuffer: %i > MAX_RENDER_COMMANDS", size);

		// If we run out of room, just start dropping commands
		return NULL;
	}

	commandList->used += size;

	return commandList->data + commandList->used - size;
}


/*
 ==============================================================================

    INITIALIZATION AND SHUTDOWN

 ==============================================================================
*/


/*
 ==================
 RB_InitBackEnd
 ==================
*/
void RB_InitBackEnd ()
{
	// Initialize vertex buffers
	RB_InitVertexBuffers();

	// Force a mode switch
	backEnd.projection2D = false;

	// Clear the batch state
	backEnd.material = NULL;
	backEnd.entity = NULL;

	backEnd.stencilShadow = false;
	backEnd.shadowCaps = false;

	// Clear the arrays
	backEnd.numIndices = 0;
	backEnd.numVertices = 0;
}

/*
 ==================
 RB_ShutdownBackEnd
 ==================
*/
void RB_ShutdownBackEnd ()
{
	int		i;

	// Disable arrays
	for (i = MAX_TEXTURE_UNITS - 1; i >= 0; i--)
	{
		if (i >= glConfig.maxTextureUnits)
			continue;

		GL_SelectTexture(i);

		qglDisableClientState(GL_TEXTURE_COORD_ARRAY);
	}

	qglDisableClientState(GL_COLOR_ARRAY);
	qglDisableClientState(GL_NORMAL_ARRAY);
	qglDisableClientState(GL_VERTEX_ARRAY);

	// Shutdown vertex buffers
	RB_ShutdownVertexBuffers();
}